﻿using System;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using HistogramLr.Properties;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace HistogramLr.Models
{
	/// <summary>
	/// 写真の管理を行うクラス。
	/// </summary>
	public class PhotoManager
	{
		private static NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();

		// ----------------------------------------------------------------
		// Constants.
		// ----------------------------------------------------------------

		/// <summary>
		/// ヒストグラムのサイズ。
		/// </summary>
		public const int HistSize = 256;

		// 描画色
		public readonly CvScalar ColorZero = new CvScalar(0x00, 0x00, 0x00, 0);
		public readonly CvScalar ColorB = new CvScalar(0x99, 0x20, 0x20, 0);
		public readonly CvScalar ColorG = new CvScalar(0x20, 0x99, 0x20, 0);
		public readonly CvScalar ColorR = new CvScalar(0x20, 0x20, 0x99, 0);
		public readonly CvScalar ColorBack = new CvScalar(0x40, 0x40, 0x40, 0);
		public readonly CvScalar ColorGrid = new CvScalar(0x48, 0x48, 0x48, 0);

		// ----------------------------------------------------------------
		// Properties.（動的に再取得などが必要な物はプロパティにしない）
		// ----------------------------------------------------------------

		// 初期化済み（ロードが完了し使用できるかどうか）を取得する
		public bool Initialized { get; private set; }

		// ファイル自体の情報
		public FileInfo FileInformation { get; private set; }
		// ファイルのメタ情報（TODO：独自のMetadataItemを作った方が管理しやすいはず）
		public ImageMetadata Metadata { get; private set; }
		// オリジナル画像とサムネイル画像
		public ImageItem Image3 { get; private set; }
		public ImageItem ImageB { get; private set; }
		public ImageItem ImageG { get; private set; }
		public ImageItem ImageR { get; private set; }
		// ヒストグラムの解析結果
		public HistogramItem HistogramB { get; private set; }
		public HistogramItem HistogramG { get; private set; }
		public HistogramItem HistogramR { get; private set; }

		// ----------------------------------------------------------------
		// Methods.
		// ----------------------------------------------------------------

		/// <summary>
		/// Constructor.
		/// </summary>
		public PhotoManager()
		{
			this.Initialized = false;
		}

		/// <summary>
		/// 指定ファイルを読み込み、解析を行う。
		/// </summary>
		/// <param name="filename"></param>
		public void LoadImage(string filename)
		{
			log.Info("LoadImage called. Filename=[{0}]", filename);

			// ファイルの存在チェック
			this.FileInformation = new FileInfo(filename);
			if (!this.FileInformation.Exists)
			{
				log.Error("File not found.");
				throw new FileNotFoundException("File not found", filename);
			}

			// メタ情報の読み込み
			log.Debug("Loading image metadata.");
			Uri fileUri = new Uri(this.FileInformation.FullName);
			JpegBitmapDecoder dec = new JpegBitmapDecoder(fileUri, BitmapCreateOptions.None, BitmapCacheOption.Default);
			this.Metadata = dec.Frames[0].Metadata;

			// 各RGB画像作成とヒストグラムの解析
			log.Debug("Loading image.");
			using (IplImage imgSrc = Cv.LoadImage(this.FileInformation.FullName, LoadMode.Color))
			{
				float[] range = { 0, 256 };
				float[][] ranges = { range };
				CvSize srcImageSize = imgSrc.Size;

				using (IplImage imgB = new IplImage(srcImageSize, BitDepth.U8, 1))
				using (IplImage imgG = new IplImage(srcImageSize, BitDepth.U8, 1))
				using (IplImage imgR = new IplImage(srcImageSize, BitDepth.U8, 1))
				using (CvHistogram histB = new CvHistogram(new int[] { HistSize }, HistogramFormat.Array, ranges, true))
				using (CvHistogram histG = new CvHistogram(new int[] { HistSize }, HistogramFormat.Array, ranges, true))
				using (CvHistogram histR = new CvHistogram(new int[] { HistSize }, HistogramFormat.Array, ranges, true))
				{
					// 画像を各RGBに分割
					log.Debug("Split image.");
					imgSrc.Split(imgB, imgG, imgR, null);

					// 各RGBのヒストグラムを計算して格納
					log.Debug("Calc histogram.");
					histB.Calc(imgB);
					histG.Calc(imgG);
					histR.Calc(imgR);
					this.HistogramB = new HistogramItem(histB, HistSize);
					this.HistogramG = new HistogramItem(histG, HistSize);
					this.HistogramR = new HistogramItem(histR, HistSize);

					// 各イメージの作成
					log.Debug("Create thumbnail image.");
					int thumbSize = Settings.Default.ThumbnailSize;
					this.Image3 = new ImageItem(imgSrc, thumbSize);
					this.ImageB = new ImageItem(imgB, thumbSize);
					this.ImageG = new ImageItem(imgG, thumbSize);
					this.ImageR = new ImageItem(imgR, thumbSize);
				}

				// Labo: 3Dヒストグラム計算
				this.HistogramHS = this.draw3DHistogramLabo(imgSrc);
			}


			// 初期化完了
			log.Debug("Initialized.");
			this.Initialized = true;
		}

		// ----------------------------------------------------------------
		#region HSVヒストグラム実験
		public WriteableBitmap HistogramHS { get; private set; }
		private WriteableBitmap draw3DHistogramLabo(IplImage imgSrc)
		{
			int hBins = 180;
			int sBins = 128;
			int[] histSize = { hBins, sBins };

			float[] hRanges = { 0, 181 }; // 色相は0～180
			float[] sRanges = { 0, 256 }; // 彩度は0～255
			float[][] ranges = { hRanges, sRanges };
			int scale = 1;

			using (IplImage histImg = new IplImage(hBins * scale, sBins * scale, BitDepth.U8, 3))
			using (IplImage hsvImg = new IplImage(imgSrc.Size, BitDepth.U8, 3))
			using (IplImage hPlane = new IplImage(imgSrc.Size, BitDepth.U8, 1))
			using (IplImage sPlane = new IplImage(imgSrc.Size, BitDepth.U8, 1))
			using (IplImage vPlane = new IplImage(imgSrc.Size, BitDepth.U8, 1))
			{
				// 画像をRGB→HSVに変換する
				Cv.CvtColor(imgSrc, hsvImg, ColorConversion.RgbToHsv);
				Cv.CvtPixToPlane(hsvImg, hPlane, sPlane, vPlane, null);

				//new CvWindow("hPlane", WindowMode.StretchImage, hPlane);
				//new CvWindow("sPlane", WindowMode.StretchImage, sPlane);
				//new CvWindow("VPlane", WindowMode.StretchImage, vPlane);

				// ヒストグラム計算
				CvHistogram hist = Cv.CreateHist(histSize, HistogramFormat.Array, ranges, true);
				//IplImage[] planes = { hPlane, sPlane };
				IplImage[] planes = { hPlane, vPlane };
				Cv.CalcHist(planes, hist);

				// 各binの値を求め、輝度計算を行う
				float minValue = 0;
				float maxValue = 0;
				Cv.GetMinMaxHistValue(hist, out minValue, out maxValue);

				// 描画
				histImg.Zero();
				for (int h = 0; h < hBins; h++)
				{
					for (int s = 0; s < sBins; s++)
					{
						double binVal = Cv.QueryHistValue_2D(hist, h, s);
						// TODO: MaxValueが大きく場合があるため、RGBヒストグラムと同様にレンジ幅を調整する
						int intensity = Cv.Round(binVal * 255.0 * 64 / maxValue);
						if (intensity > 256) intensity = 255;

						Cv.Rectangle(histImg,
							new CvPoint(h * scale, s * scale),
							new CvPoint((h + 1) * scale - 1, (s + 1) * scale - 1),
							new CvScalar(intensity, intensity, intensity));
					}
				}

				return WriteableBitmapConverter.ToWriteableBitmap(histImg);
			}
		}
		#endregion
		// ----------------------------------------------------------------

		// ----------------------------------------------------------------
		// Methods. （結果取得系）
		// ----------------------------------------------------------------

		/// <summary>
		/// ヒストグラムのグラフ画像を生成する。
		/// </summary>
		/// <param name="height">グラフの高さ（Pixel）</param>
		/// <param name="graphScale">ヒストグラムのスケール値</param>
		/// <returns>HistogramImageItem object.</returns>
		public HistogramImageContainer DrawHistogramGraph(int height, double graphScale)
		{
			HistogramImageContainer histogramGraphItem = new HistogramImageContainer();

			log.Info("Drawing histogram graph.");
			using (IplImage imgResult3 = new IplImage(HistSize, height, BitDepth.U8, 3))
			using (IplImage imgResultB = new IplImage(HistSize, height, BitDepth.U8, 3))
			using (IplImage imgResultG = new IplImage(HistSize, height, BitDepth.U8, 3))
			using (IplImage imgResultR = new IplImage(HistSize, height, BitDepth.U8, 3))
			using (IplImage imgHGraphB = new IplImage(HistSize, height, BitDepth.U8, 3))
			using (IplImage imgHGraphG = new IplImage(HistSize, height, BitDepth.U8, 3))
			using (IplImage imgHGraphR = new IplImage(HistSize, height, BitDepth.U8, 3))
			{
				// RGBグラフの描画（ワーク用）
				imgHGraphB.SetZero();
				imgHGraphG.SetZero();
				imgHGraphR.SetZero();
				for (int i = 0; i < HistSize; i++)
				{
					imgHGraphB.DrawLine(i, height, i, height - (int)this.HistogramB.GetFixedValue(i, height, graphScale), ColorB);
					imgHGraphG.DrawLine(i, height, i, height - (int)this.HistogramG.GetFixedValue(i, height, graphScale), ColorG);
					imgHGraphR.DrawLine(i, height, i, height - (int)this.HistogramR.GetFixedValue(i, height, graphScale), ColorR);
				}

				// ヒストグラムの背景描画
				this.drawHistogramBase(imgResult3);
				this.drawHistogramBase(imgResultB);
				this.drawHistogramBase(imgResultG);
				this.drawHistogramBase(imgResultR);

				// 各RGBのグラフ描画
				imgResultB.Add(imgHGraphB, imgResultB);
				imgResultG.Add(imgHGraphG, imgResultG);
				imgResultR.Add(imgHGraphR, imgResultR);
				// RGBの合成グラフ描画
				imgResult3.Add(imgHGraphB, imgResult3);
				imgResult3.Add(imgHGraphG, imgResult3);
				imgResult3.Add(imgHGraphR, imgResult3);

				// HistogramImageItemの作成
				histogramGraphItem.Histogram3Graph = WriteableBitmapConverter.ToWriteableBitmap(imgResult3);
				histogramGraphItem.HistogramBGraph = WriteableBitmapConverter.ToWriteableBitmap(imgResultB);
				histogramGraphItem.HistogramGGraph = WriteableBitmapConverter.ToWriteableBitmap(imgResultG);
				histogramGraphItem.HistogramRGraph = WriteableBitmapConverter.ToWriteableBitmap(imgResultR);
			}

			return histogramGraphItem;
		}



		/// <summary>
		/// ヒストグラムの背景を描画する。
		/// TODO: DrawUtilsに移動させる。描画系は全て同一クラスへ集約させる。
		/// </summary>
		/// <param name="targetImage">IplImage object.</param>
		private void drawHistogramBase(IplImage targetImage)
		{
			// Clear image.
			targetImage.Set(ColorBack);

			// Draw grid line.
			int height = targetImage.Height;
			for (int i = 0; i < HistSize; i += 32)
			{
				targetImage.DrawLine(i, 0, i, height, ColorGrid);
			}
		}

	}
}
